/**
 * sybn基础静态工具类 20250731版
 * 此类中不含任何业务逻辑
 *
 * 函数列表:
 * replaceAll - 批量替换
 * replaceDefaults - freemark风格的批量替换
 * serializeObject - 输出表单的所有value为Map格式,类似于jquery自带的serializeArray()方法
 * parseParam - 将Map转换为url参数
 * changeURLPar - 替换 url 中的单个参数
 * getQueryString - 获取指定请求参数
 * dateFormatting - 时间格式化
 * objToDate - 对象装时间
 * loadDataToTable - 将数据载入到table中,使用template作为模板
 * putFromAllValue - 替换input的value
 * findByText - 获取所有内容为text的元素
 * stopPropagation - 阻止冒泡
 * showLoadingAsBtn - 显示按钮的loading效果
 * hideLoadingAsBtn - 移除按钮的loading效果
 * html2Escape - html特殊字符转义
 */

/**
 * 批量替换 类似replace();
 */
String.prototype.replaceAll = function(regex,s2){
    return this.replace(new RegExp(regex,"gm"),s2);
}
/**
 * 多行字符串赋值工具
 */
function hereDoc(func) {
 	return func.toString().split(/\n/).slice(1, -1).join('\n');
}
/**
 * freemark风格的数据替换
 * "<span>{a!0}</span>".replaceDefaults('a', '1') => <span>1</span>
 * "<span>{a!0}</span>".replaceDefaults('a', null) => <span>0</span>
 *
 * @20170908 修复了key包含正则特殊符号造成匹配异常的bug, 复现代码:
 *           "1{.}1{?}1{.}1".replaceDefaults(".","-")
 *
 * @param key
 *          被替换的key
 * @param value
 *          被替换成的值
 *
 */
String.prototype.replaceDefaults = function(key,value){
  if (!key) {
    return this;
  }
  if (key.indexOf(".") >= 0) {
    key = key.replaceAll("\\\.", "\\\.");
  }
  if (key.indexOf("*") >= 0) {
    key = key.replaceAll("\\\*", "\\\*");
  }
  if (key.indexOf("{") >= 0) {
    key = key.replaceAll("\\\{", "\\\{");
  }
  if (key.indexOf("}") >= 0) {
    key = key.replaceAll("\\\}", "\\\}");
  }
  if (key.indexOf("(") >= 0) {
    key = key.replaceAll("\\\(", "\\\(");
  }
  if (key.indexOf(")") >= 0) {
    key = key.replaceAll("\\\)", "\\\)");
  }
  if (key.indexOf("^") >= 0) {
    key = key.replaceAll("\\\^", "\\\^");
  }
  if (key.indexOf("$") >= 0) {
    key = key.replaceAll("\\\$", "\\\$");
  }
  if (key.indexOf("\\") >= 0) {
    key = key.replaceAll("\\\\", "\\\\");
  }
  if (key.indexOf("?") >= 0) {
    key = key.replaceAll("\\\?", "\\\?");
  }
  if (value) {
    return this
      .replace(new RegExp("#\\\{"+key+"\\\?empty\\\}", "gm"), "" + (value == 'null'))
      .replace(new RegExp("#\\\{"+key+"\\\?exists\\\}", "gm"), 'true')
      .replace(new RegExp("#\\\{"+key+"\\\}", "gm"), value)
      .replace(new RegExp("#\\\{"+key+"!([^\}]*)\\\}", "gm"), value);
  } else {
    // value = undefined / null / 0 / false / “”
    return this
      .replace(new RegExp("#\\\{"+key+"\\\?empty\\\}", "gm"), 'true')
      .replace(new RegExp("#\\\{"+key+"\\\?exists\\\}", "gm"), 'false')
      .replace(new RegExp("#\\\{"+key+"\\\}", "gm"), value)
      .replace(new RegExp("#\\\{"+key+"!([^\}]*)\\\}", "gm"), "$1");
  }
}
/**
 * 输出表单的所有value为Map格式,类似于jquery自带的serializeArray()方法
 */
$.fn.serializeObject = function() {
  var o = {serialize:function(){
    return parseParam(this);
  }};
  var a = this.serializeArray();
  $.each(a, function() {
    if (o[this.name] !== undefined) {
      if (!o[this.name].push) {
        o[this.name] = [o[this.name]];
      }
      o[this.name].push(this.value || '');
    } else {
      o[this.name] = this.value || '';
    }
  });
  return o;
};
var hashCode = function(str){
    var hash = 0;
    if (str.length == 0) return hash;
    for (i = 0; i < str.length; i++) {
        char = str.charCodeAt(i);
        hash = ((hash<<5)-hash)+char;
        hash = hash & hash; // Convert to 32bit integer
    }
    return hash;
}
/**
 * 将Map转换为url参数 parseParam({a:1,b:2,c:{a:1,b:2}}); => "a=1&b=2&c.a=1&c.b=2"
 *
 * @param param
 *          map型参数
 * @param key
 *          上级key,(此为递归参数,勿手工传入)
 */
var parseParam = function(param, key){
  var paramStr="";
  if(param instanceof String||param instanceof Number||param instanceof Boolean){
    paramStr += "&" + key + "=" + encodeURIComponent(param);
  } else {
    $.each(param, function(i){
      var k = key==null ? i : key + (param instanceof Array ? "" : "." + i );
      paramStr += '&' + parseParam(this, k);
    });
  }
  return paramStr.substr(1);
};
/**
 * 替换url中的参数
 */
function changeURLPar(url, key, value) {
  var replaceText = key + '=' + (value == null || typeof(value) == undefined ? "" : encodeURIComponent(value));
  var idx = url.indexOf("#") ;
  var postfix = "";
  if (idx > 0) {
    postfix = url.substr(idx);
    url = url.substr(0, idx);
  }
  if (url.match('[?&]' + key + '=([^&]*)')) {
    return url.replace(eval('/' + key + '=[^&]*/'), replaceText) + postfix;
  } else {
    if (url.match('\\?'))  {
      return url + '&' + replaceText + postfix;
    } else {
      return url + '?' + replaceText + postfix;
    }
  }
}
/**
 * 删除url中的参数
 */
function removeURLPar(url, key) {
  if (url.match('[?&]' + key + '=([^&]*)')) {
    return url.replace(eval('/' + key + '=[^&]*/'), "");
  }
  return url;
}

/**
 * 时间对象的格式化; eg:format="yyyy-MM-dd hh:mm:ss";
 */
function dateFormatting(date, format) {
    var o = {
        "y+":date.getFullYear,
        "M+":date.getMonth() + 1,
        "d+":date.getDate(),
        "h+":date.getHours(),
        "m+":date.getMinutes(),
        "s+":date.getSeconds(),
        "q+":Math.floor((date.getMonth() + 3) / 3),
        "S":date.getMilliseconds()
    }
    if (/(y+)/.test(format)) {
        format = format.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
    }
    for (var k in o) {
        if (new RegExp("(" + k + ")").test(format)) {
            format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
        }
    }
    return format;
}
/**
 * 格式化时间
 */
Date.prototype.format = function(format){
  return dateFormatting(this, format)
};
/**
 * 转换成时间
 */
function objToDate(obj){if (obj == null) return null;if (typeof obj == 'number') return new Date(obj);if (typeof obj != 'string') return obj;if (obj.length == 24) return uuid2date(obj);if (obj.length < 11) return new Date(Date.parse(obj.replace(/-/g, "/") + " 00:00"));return new Date(Date.parse(obj.replace(/-/g, "/")));}

/**
 * 将数据载入到table中,使用template作为模板,模板中没有的列会被新建
 *
 * <pre>
 * template自定义属性:
 * data-template_field_hidden 可选,需要隐藏字段列表,默认为空
 * data-template_field 可选,需要显示字段列表,与前者冲突时,拒绝大于允许原则.默认显示全部字段.例如:a,b,c 或者 {a:标题a,b:标题b}
 * data-template_conver_field 可选,(优先级高于后两者)需要自定义数据显示格式的字段map,需要传入一个json,key是字段名,value是转换方法名或方法名加参数.例如:{&quot;time1&quot;:&quot;mydataformat(value,'yyyy-MM-dd')&quot;,&quot;time2&quot;:&quot;mydataformat&quot;}
 * data-template_text_field 可选,需要强转为文本显示的字段列表,默认为空
 * data-template_html_field 可选,需要作为html显示的字段列表(需要控制数据源,注意防止xss),默认全部使用html输出
 * </pre>
 *
 * @author sybn
 * @param datas
 *          数据列表
 * @param template
 *          模板名 "a" OR $("a")
 * @param target
 *          输出位置 "a" OR $("a")
 */
function loadDataToTable(datas, target, template) {
  var tbody = typeof(target) == "string" ? $(target + " tbody") : target.find("tbody");
  var thead = typeof(target) == "string" ? $(target + " thead") : target.find("thead");
  template = template || ".template_default";
  var $template = $(template);

  /**
   * 转义数据中的特殊符号,尝试xss攻击
   */
  // 允许直接显示而不转义的字段白名单
  var htmlField = $template.data("template_html_field");
  var htmlFields = htmlField ? htmlField.split(",") : [];

  // 忽略此名单中的数据中的html部分
  var textField = $template.data("template_text_field");
  var textFields = textField ? textField.split(",") : [];

  // 指定字段的处理方法(例如:时间格式化)
  var converFields = $template.data("template_conver_field");
  var converFieldMap = converFields ? converFields  : {};

  // 待显示字段白名单
  var template_field = $template.data("template_field");
  var allFieldNameMap = initTemplateField(template_field, escapeDatas)

  // 所有字段
  var allFieldConverMap = {};
  for (var field in allFieldNameMap) {
    allFieldConverMap[field] = "";
  }
  // 转义后的数据, value 全转为 string
  var escapeDatas = initAllData(datas, allFieldNameMap, allFieldConverMap, converFieldMap, textFields, htmlFields);

  /**
   * 获取所有需要填充的列
   */

  // 待显示字段黑名单
  if ($template.data("template_field_hidden")) {
    // 移除黑名单中的内容
    var templateFields = $template.data("template_field_hidden").split(",")
    for(var i=0;i<templateFields.length;i++){
      delete allFieldNameMap[templateFields[i]];
    }
  }

  // 构造表头
  var template_tr = $(template + " thead tr").eq(0);
  var thead_tr = initTemplateHead(template_tr, allFieldNameMap);
  thead.children().remove();
  thead.append(thead_tr);

  // 获取模板中的tr头标签,例如: "<tr class='red'>"
  var tbody_tr_template = "<tr>";
  var template_tr = $(template + " tbody tr").eq(0);
  if (template_tr && template_tr.length > 0) {
    // 使用模板的tr
    var clone = template_tr.clone();
    clone.children().remove()
    var template_tr_str = clone.prop("outerHTML");
    tbody_tr_template = template_tr_str.replace("</tr>","").trim();
  }

  // 生成逐行数据
  var trs = [];
  // 最多显示1000条，显示数据太多容易卡死浏览器
  for (i = 0; i < escapeDatas.length && i < 1000; i++) {
    var data = escapeDatas[i];
    var data2 = datas[i];
    // 生成一行数据
    var tr = makeTr(template_tr, tbody_tr_template, data, allFieldNameMap, allFieldConverMap, data2);
    trs.push(tr);
  }
  tbody.children().remove();
  tbody.append(trs);
  if (datas.length != 0) {
    tbody.addClass("col_num_" + datas[0].length)
  }

  // 如果需要显示的列数过多，则减小字体和间距
  if (Object.keys(allFieldNameMap).length > 15) {
	  thead.parent().addClass("mini_data");
  } else {
	  thead.parent().removeClass("mini_data");
  }
}
function fieldToClass(field) {
  return "." + field.replace(/[^A-Za-z0-9\u4e00-\u9fa5-_]/g, "_");
}
/**
 * 构建一行数据
 *
 * @param template_tr
 * @param tbody_tr_template
 * @param data
 * @param fields
 * @returns
 */
function makeTr(template_tr, tbody_tr_template, data, allFieldNameMap, allFieldConverMap, data2) {
  var tr = tbody_tr_template;
  for (var field in allFieldNameMap) {
    var val = data[field];
    // 整理待写入table的数据
    if (typeof(val) == "undefined" || val == null) {
      val = "";
    } else if (typeof val == "object") {
      val = JSON.stringify(val);
    }

    // 根据class寻找td模板
    var template_td = template_tr.find(fieldToClass(field));
    if (template_td && template_td.length > 0) {
      var clone = template_td.eq(0).clone();
      if (allFieldNameMap[field] == "hide") {
        // 替换数据
        tr += clone.addClass("hide")
      }
      tr += clone.prop("outerHTML").replaceDefaults(field,val);
      continue;
    }

    // 根据内容寻找td模板
    var template_td = findByText("{" + field + "}", template_tr);
    if (template_td && template_td.length > 0) {
      var td = template_td.eq(0).clone();
      // 如果表头隐藏则也跟着隐藏
      if (allFieldNameMap[field] == "hide") {
        td.addClass("hide");
      }
      // 替换数据
      tr += td.text(val).prop("outerHTML");
      continue;
    } else {
      var html = template_tr.find(fieldToClass(field)).html();
      if (!html) {
        // 如果有首尾空白符则高亮一下, 提醒注意
        if (val && val.indexOf('<br>') === val.length - 4) {
            val = val.slice(0, -4) + '\r\n';
        }
        html = val.trim() == val ? val : '<span title="注意首尾空白">'+val.replace(/^(\s*)(.*?)(\s*)$/, function(match, leading, content, trailing) {
            return (leading ? '<span style="background-color: #fDD; white-space: pre-wrap">' + leading + '</span>' : '') + content + (trailing ? '<span style="background-color: #fDD; white-space: pre-wrap">' + trailing + '</span>' : '');
        })+'</span>';
      }
      // 如果表头隐藏则也跟着隐藏
      var v = data2[field];
      if (allFieldNameMap[field] == "hide") {
        // 替换数据
        tr += "<td class='sybn_td_" + field + " hide' data-field='"+field+"' data-type='" + (v==null? "null" : typeof(v)) +"'>" + html + "</td>";
      } else {
        // 替换数据
        tr += "<td  class='sybn_td_" + field + "' data-field='"+field+"' data-type='" + (v==null? "null" : typeof(v)) + "'>" + html + "</td>";
      }
      continue;
    }
  }
  var template_td = template_tr.find("._action");
  if (template_td && template_td.length > 0) {
    for (var i = 0;i< template_td.length;i++) {
      var td = template_td.eq(i).clone();
      tr += td.prop("outerHTML");
    }
  }
  tr += "</tr>";
  // 再次替换,主要替换模板属性中的内容
  for (var field in allFieldConverMap) {
    var val = data[field];
    tr = tr.replaceDefaults(field,val);
  }
  // 查找剩余内容
  var other = tr.match(/#\{([^\}!\?]*)[!\?]*([^\}!\?]*)\}/g); // 知道到所有的{xxx}
  var otherFields = {}
  if (other != null) {
    for (i = 0; i < other.length; i++) {
      // 提取出{xxx}中过得xxx,忽略!以后部分
      var otherField = other[i].replace(/#\{([^\}!\?]*)[!\?]*([^\}!\?]*)\}/, "$1")
      otherFields[otherField] = "";
    }
  }
  // 替换剩余内容
  for (var field in otherFields) {
    var val = data[field];
    tr = tr.replaceDefaults(field,val);
  }
  return tr;
}

/**
 * 初始化所有数据(将数据去除html标签,或者使用自定义函数处理数据)
 * @param datas
 * @param allFieldConverMap
 * @return 处理后的数据
 */
function initAllData(datas, allFieldNameMap, allFieldConverMap, converFieldMap, textFields, htmlFields) {
  var addAllField = $.isEmptyObject(allFieldNameMap);
  var escapeDatas = [];
  var $escape = $("<span></span>");
  // 整理白名单中的字段名
  for (var field in allFieldConverMap) {
    // 更新allFieldConverMap
    allFieldConverMap[field] = converFieldMap[field] || "";
  }
  // 整理数据中的字段名
  for (var i = 0; i < datas.length; i++) {
    var data = datas[i];
    for (var field in data) {
      // 如果数据中有特殊字段,则处理成正常的字段名
      var fieldOld = field;
      field = (field == null) ? "-" : field;
      field = field.trim();
      field = (field == "") ? "-" : field;
      field = (field == "_id") ? "id" : field;
      field = field.replace(/\s+/g,"-")
      if (fieldOld != field){
        data[field] = data[fieldOld];
        delete data[fieldOld];
      }
      // 更新allFieldConverMap
      allFieldConverMap[field] = converFieldMap[field] || "";
      if (addAllField && allFieldNameMap[field] == undefined) {
          allFieldNameMap[field] = "";
      }
    }
  }
  // 处理数据
  for (var i = 0; i < datas.length; i++) {
    var data = datas[i];
    var escapeData = {}
    for (var field in allFieldConverMap) {
      var fun = allFieldConverMap[field];
      var value = data[field];
      if (fun && fun != "") {
        // 如果有指定转换类,则使用转换类获取输出数据
        if (fun.indexOf("(") < 0) {
          // 如果无参数则使用默认参数
          escapeData[field] = eval(fun + '(value)');
        } else {
          // 如果有参数则使用参数
          escapeData[field] = eval(fun);
        }
        // 记录转换前的数据,为特殊需求预留
        escapeData[field + "_originalValue"] = JSON.stringify(value);
      } else if (value == null) {
        escapeData[field] = '';
      } else if (typeof value == 'object') {
        escapeData[field] = JSON.stringify(value);
      } else if ($.inArray(field, textFields) >= 0) {
        // 所有被列入textFields的属性一律去除html部分,利用$escape做转义
        escapeData[field] = $escape.html(value).text();
      } else if ($.inArray(field, htmlFields) < 0) {
        var html = "";
        if (typeof value == "number" && value > 9007199254740992) {
            // 数字过大精度丢失
            html = value + " <i class='fa fa-exclamation-triangle' aria-hidden='true' title='数字过大, 无法正常显示, 已经丢失精度'></i>";
        } else {
            // 所有未被入htmlFields的属性一律转义,利用$escape做转义
            html = $escape.text(value).html();
        }
        escapeData[field] = (html) ? html.replace(/(\r\n|\n)/g,"<br>") : "";
      } else {
        // 其他字段不处理,直接做html显示,有xss风险
      }
    }
    escapeDatas.push(escapeData);
  }
  return escapeDatas;
}

function initTemplateHead(template_tr, fields) {
//根据模板生成表头
  var thead_tr = "<tr>";
  if (template_tr && template_tr.length > 0) {
    // 使用模板的tr
    var clone = template_tr.clone();
    clone.children().remove()
    var template_tr_str = clone.prop("outerHTML");
    thead_tr = template_tr_str.replace("</tr>","").trim();
  }
  // 根据数据补充表头
  for (var field in fields) {
    var template_th = template_tr.find(fieldToClass(field));
    if (template_th && template_th.length > 0) {
      // 如果模板中已经有这个field了
      var td = template_th.eq(0).clone();
      thead_tr += td.prop("outerHTML");
      if (td.hasClass("hide")) {
        // 在需要隐藏时隐藏这个field对应的列
        fields[field] = "hide";
      }
    } else {
      // 如果模板中没有这个Field对应的列
      var fieldName = fields[field];
      if (fieldName == "hide") {
        // 隐藏列
        thead_tr += "<th data-field='"+field+"' class='sybn_th_" + field + " hide'>" + field + "</th>";
      } else if (fieldName == "") {
        // 使用字段名创建列头
        thead_tr += "<th data-field='"+field+"' class='sybn_th_" + field + "' title='" + field + "'>" + field + "</th>";
      } else {
        // 使用字段别名创建列头
        thead_tr += "<th data-field='"+field+"' class='sybn_th_" + field + "'>" + fieldName + "</th>";
      }
    }
  }

  var template_th = template_tr.find("._action");
  if (template_th && template_th.length > 0) {
    for (var i = 0;i< template_th.length;i++) {
      var td = template_th.eq(i).clone();
      thead_tr += td.prop("outerHTML");
    }
  }
  thead_tr += "</tr>";
  return thead_tr;
}

function initTemplateField(template_field, escapeDatas) {
  var fields = {};
  // 待显示字段白名单
  if (template_field || "" == template_field) {
    if (typeof template_field == "string") {
      // 根据白名单
      var templateFields = template_field.split(",");
      for(var i=0;i<templateFields.length;i++){
        fields[templateFields[i]] = "";
      }
    } else if (typeof template_field == "object"){
      for(var p in template_field){
        fields[p] = template_field[p];
      }
    }
  }
  return fields;
}

/**
 * 替换表单中所有对应的字段
 *
 * @param data
 *          {username : 1, password: aaa}
 * @param target
 * @param prefix
 *          例如:"user_",则操作 [name=user_username]
 */
function putFromAllValue(data, target, prefix) {
  target = target || $;
  for (var p in data) {
    prefix = prefix  || "";
    var $input = target.find("input[name=" + prefix + p + "]")
    if(input) {
      $input.val(data[p]);
    }
    // TODO 暂时仅支持input
  }
}

/**
 * 获取所有内容为text的元素
 *
 * @param text
 * @param target
 * @returns jquery([...])
 */
function findByText(text, target) {
  target = target || $;
  var f = target.find(":contains('" + text + "')");
  if (f.length == 0) {
    return f;
  }
  var res = [];
  for (var int = 0; int < f.length; int++) {
    if (f[int].innerText == text && f[int].children.length == 0) {
      res.push(f[int]);
    }
  }
  return $(res);
}
/**
 * 阻止冒泡方法
 */
function stopPropagation(e) {
    e = e || window.event;
    if(e.stopPropagation) { // W3C阻止冒泡方法
        e.stopPropagation();
    } else {
        e.cancelBubble = true; // IE阻止冒泡方法
    }
}

/**
 * 获取指定请求参数.
 *
 * @param name 参数名 必填
 * @param defaults 默认值, 可选
 * @param url 可选, 默认为当前页url
 */
function getQueryString(name, defaults, url) {
  var reg = new RegExp("(^|\\?|&)" + name + "=([^&]*)(&|$)", "i");
  var r = (url) ? url.match(reg) : window.location.search.substr(1).match(reg);
  if (r != null) return decodeURIComponent(r[2]);
  return typeof defaults != undefined ? defaults : null;
}

/**
 * 将 source 与 target 的 value 同步
 * @param source
 * @param target
 * @returns
 */
function syncVal(source, target) {
  var newValue = $(source).val();
  // 可能有多个匹配的$input
  var $input = $(source).closest('form').find(target);
  if ($input.length == 0) {
    return;
  }
  // 针对每一个做处理
  $input.each(function(){
    var oldValue = $(this).val();
    if (newValue != '' && newValue != oldValue) {
      $(this).val(newValue);
      $(this).change();
    }
  });
}


// 常量，默认的用于移除sybn_showLoadingAsBtn_html的选择器
var sybn_defaults_showLoadingAsBtn_selector = ".loadData_loading";
// 常量，默认的loading效果
var sybn_defaults_showLoadingAsBtn_html = "<span class='btn btn-warning btn-xs loadData_loading'><span class='fa fa-spinner fa-spin'></span> 处理中...</span>";
/**
 * 将一组按钮隐藏替换成loading效果
 * @param $click 需要被隐藏的按钮
 * @param showLoadingAsBtn_html loading效果
 * @returns
 */
function showLoadingAsBtn($click,showLoadingAsBtn_html) {
  if ($click) {
    showLoadingAsBtn_html = showLoadingAsBtn_html || sybn_defaults_showLoadingAsBtn_html;
    var $loading = $(showLoading_html);
    $click.hide();
    $click.after($loading);
  }
}

/**
 * 将一组按钮恢复并移出loading效果
 * @param $click 需要被显示的按钮
 * @param showLoadingAsBtn_selector 移除sybn_showLoadingAsBtn_html的选择器
 * @returns
 */
function hideLoadingAsBtn($click,showLoadingAsBtn_selector) {
  if ($click) {
    showLoadingAsBtn_selector = showLoadingAsBtn_selector || sybn_defaults_showLoadingAsBtn_selector
    var $loading = $click.parent().find(showLoadingAsBtn_selector);
    $loading.remove();
    $click.show();
  }
}

//post方法传参跳转
function openPost(url,data){
 $.ajax({
     url: url,
     type:'post',
     dataType:'json',
     contentType:"application/json",
     async:true,//异步请求
     cache:false,
     data: data,
     //执行成功的回调函数
     success:function(data) {
//         console.log(data)
         window.open(url)
     },
     //执行失败或错误的回调函数
     error:function(data) {
         alert("失败");
     }
 });
}

// POST 到新窗口 TODO
function openPostWindow(url, data, name) {
   var tempForm = document.createElement("form");
   tempForm.id="tempForm1";
   tempForm.method="post";
   tempForm.action=url;
   tempForm.target=name || "_blank";

   var hideInput = document.createElement("input");
   hideInput.type="hidden";
   hideInput.name= "content"
   hideInput.value= data;

   tempForm.appendChild(hideInput);
   tempForm.attachEvent("onsubmit",function(){ openWindow(name); });
   document.body.appendChild(tempForm);
   tempForm.fireEvent("onsubmit");
   tempForm.submit();
   document.body.removeChild(tempForm);
}

function openWindow(name) {
   window.open('about:blank',name,'height=400, width=400, top=0, left=0, toolbar=yes, menubar=yes, scrollbars=yes, resizable=yes,location=yes, status=yes');
}

var sybnCookie = {
    setCookie : function(name, value, exDays) {
        var d = new Date();
        d.setTime(d.getTime() + (exDays * 24 * 60 * 60 * 1000));
        var expires = "expires="+d.toUTCString();
        document.cookie = name + "=" + value + ";" + expires + ";path=/";
    },
    getCookie : function(cname) {
        var name = cname + "=";
        var ca = document.cookie.split(';');
        for(var i = 0; i < ca.length; i++) {
            var c = ca[i];
            while (c.charAt(0) == ' ') {
                c = c.substring(1);
             }
            if (c.indexOf(name)  == 0) {
                return c.substring(name.length, c.length);
             }
        }
        return "";
    },
    checkCookie : function () {
        var user = getCookie("username");
        if (user != "") {
            alert("Welcome again " + user);
        } else {
            user = prompt("Please enter your name:", "");
            if (user != "" && user != null) {
                setCookie("username", user, 365);
            }
        }
    }
}

/** 显示服务器版本信息 */
function showVersion(url, element, type){
 $.ajax({
     url: url,
     type:'get',
     dataType:'json',
     contentType:"application/json",
     async:true,//异步请求
     cache:false,
     //执行成功的回调函数
     success:function(data) {
        if (data && data.result && data.attachment && data.attachment.version) {
            if ("text" == type) {
                element.text(data.attachment.version);
            } else {
                element.attr("title", data.attachment.version);
            }
        }
     },
     //执行失败或错误的回调函数
     error:function(data) {

     }
 });
}

/** 显示下拉参数 */
function showSelect(url, element, defaultValue, addDefault, onLoad){
 $.ajax({
     url: url,
     type:'get',
     dataType:'json',
     contentType:"application/json",
     async:true,//异步请求
     cache:false,
     //执行成功的回调函数
     success:function(data) {
        var vals = [];
        if (data && data.result && data.rows) {
            for(var i = 0; i < data.rows.length; i++) {
                var content = data.rows[i].v;
                var contentSplit = content.split(" -- ");
                if (contentSplit.length == 2) {
                    content = contentSplit[0].replace("\"", "\\\"") + " <span style='color:#69C'>" + contentSplit[1].replace("\"", "\\\"") + "</span>"
                }
                element.append("<option value='" + data.rows[i].k + "' data-content=\"" + content + "\">" + data.rows[i].v + "</option>");
                vals.push(data.rows[i].k);
            }
        }
        if (addDefault && defaultValue && vals.indexOf(defaultValue) < 0) {
            // 如果 defaultValue 不在 vals 中，则添加
            var content = defaultValue;
            var contentSplit = content.split(" -- ");
            if (contentSplit.length == 2) {
                content = contentSplit[0].replace("\"", "\\\"") + " <span style='color:#69C'>" + contentSplit[1].replace("\"", "\\\"") + "</span>"
            }
            element.prepend("<option value='" + defaultValue + "'>" + defaultValue + "</option>");
        }
        if (typeof(defaultValue) != undefined && defaultValue != null) {
            element.val(defaultValue);
        }
        if (onload) {
            onLoad(data);
        }
     },
     //执行失败或错误的回调函数
     error:function(data) {
        if (onload) {
            onLoad(data);
        }
     }
 });
}

/** 下载 csv */
function downloadJson2Csv(objArray, filename) {
    var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
    if (array.length == 0) {return}
    var str = array2Csv(array, ",", '\ufeff'); // feff 是 utf-8 的 BOM 头

    // 下载文件
    let blob = new Blob([str], { type: 'text/csv,charset=UTF-8' }); // 将字符串转成blob地址

    // 处理文件名
    let safeFilename = '';
    if (filename) {
        // 截取特殊符号, 仅保留前32个字符
        safeFilename = filename.replace(/</g, '小于').replace(/>/g, '大于').replace(/[^a-zA-Z0-9_,=\-\.\\u4e00-\\u9fa5]/g, '').substring(0, 60);
    } else {
        safeFilename = '导出数据' ;
    }
    let fileName = safeFilename + '_' + longdate() + '.csv';

    if (window.navigator.msSaveOrOpenBlob) {
        // msSaveOrOpenBlob方法返回bool值
        navigator.msSaveBlob(blob, fileName) // 本地保存
    } else {
        const link = document.createElement('a') // a标签下载
        link.href = window.URL.createObjectURL(blob)
        link.download = fileName
        link.click()
        window.URL.revokeObjectURL(link.href)
    }
}

function array2Csv(array, delimit, prefix) {
    delimit = delimit == undefined || delimit == null ? ',' : delimit;
    prefix = prefix == undefined || prefix == null ? '' : prefix;
    // 表头
    var str = prefix;
    var line = '';
    var keys = {};
    for (var i = 0; i < array.length; i++) {
      for (var key in array[i]) {
        keys[key] = 1;
      }
    }
    for (var key in keys) {
        line += needquotes(key) ? ('"' + key.replace(/"/g, '""') + '"' + delimit) : (key + delimit);
    }
    line.slice(0,line.Length-1);
    str += line + '\r\n';
    // 内容
    for (var i = 0; i < array.length; i++) {
      var line = '';
      for (var key in keys) {
        var x = array[i][key];
        if (x == null || x == NaN || x == Infinity || typeof(x) == 'undefined') {
           x = "";
        } else if (typeof (x) == 'object') {
           x = JSON.stringify(x);
        } else if (typeof (x) != 'string') {
           x = x.toString();
        }
        line += needquotes(x) ? ('"' + x.replace(/"/g, '""') + '"' + delimit) : (x + delimit);
      }
      line.slice(0,line.Length-1);
      str += line + '\r\n';
    }
    return str;
}


/** 下载 csv */
function downloadJson2Tsv(objArray) {
    var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
    if (array.length == 0) {return}

    var str = array2Csv(array, "\t");
    copyToClipboardStr(str);
}

function copyToClipboardStr(str){
    // 创建一个隐藏的textarea元素
    var $temp = $("<textarea>");
    // 将其添加到DOM中，以便可以选中其内容
    $("body").append($temp);
    // 将要复制的文本放入textarea
    $temp.val(str);
    // 选中textarea中的文本
    $temp.select();
    // 执行复制命令
    document.execCommand("copy");
    // 清理，移除临时创建的textarea
    $temp.remove();

    // 显示复制成功的提示
    showCopySuccessMessage(event, "复制成功");
}

function showCopySuccessMessage(event, message) {
    // 创建提示元素
    if (message) {
        // 判断是否包含 "失败"（可扩展为正则或关键词列表）
        if (message.includes('<') && message.includes('>')) {
            $message = $(message);
       } else if (message.includes('失败') || message.includes('错误') || message.toLowerCase().includes('error') || message.toLowerCase().includes('exception')) {
            $message = $("<div class='copy-error'></div>").text(message);
        } else {
            $message = $("<div class='copy-success'></div>").text(message);
        }
    } else {
        // 默认成功提示
        $message = $("<div class='copy-success'>操作成功</div>");
    }

    // 获取鼠标位置
    var mouseX = event.clientX;
    var mouseY = event.clientY;

    // 设置提示元素的位置
    $message.css({
        top: mouseY + 10, // 稍微偏移鼠标位置
        left: mouseX + 10
    });

    // 将提示元素添加到body
    $("body").append($message);

    // 设置淡出效果
    setTimeout(function() {
        $message.addClass("fade-out");
        setTimeout(function() {
            $message.remove();
            $(".fade-out").remove();
        }, 2000); // 2秒后移除元素
    }, 10); // 稍微延迟一下，确保淡出效果生效
}

/**
 * 是否需要引号
 * 如果 value 包含 ,"\r\n 或者12位以上超长数字, 则要求加引号
 */
function needquotes(value) {
    if (!value) {
        return false;
    }
    var v = "" + value;
    if (v.indexOf(',') != -1 || v.indexOf('"') != -1 ||  v.indexOf('\r') != -1 || v.indexOf('\n') != -1 || new RegExp(/^-?[0-9]{12,}/).test(v)) {
        return true;
    }
    return false;
}

function longdate(t) {
    var nowTime = t || new Date();
    var year = nowTime.getFullYear();
    var month = nowTime.getMonth()+1;
    var day = nowTime.getDate();
    var hour =nowTime.getHours();
    var minutes = nowTime.getMinutes();
    var seconds = nowTime.getSeconds();
    return year*10000000000+month*100000000+day*1000000+hour*10000+minutes*100+seconds;
}
/**
 * 一键粘贴
 * @param  {String} id [需要粘贴的内容]
 * @param  {String} attr [需要 copy 的属性，默认是 innerText，主要用途例如赋值 a 标签上的 href 链接]
 *
 * range + selection
 *
 * 1.创建一个 range
 * 2.把内容放入 range
 * 3.把 range 放入 selection
 *
 * 注意：参数 attr 不能是自定义属性
 * 注意：对于 user-select: none 的元素无效
 * 注意：当 id 为 false 且 attr 不会空，会直接复制 attr 的内容
 */
function copyText(element, attr) {
    let target = null;

    if (attr) {
        target = document.createElement('div');
        target.id = 'tempTarget';
        target.style.opacity = '0';
        if (id) {
            target.innerText = element[attr];
        } else {
            target.innerText = attr;
        }
        document.body.appendChild(target);
    } else {
        target = element;
    }

    try {
        let range = document.createRange();
        range.selectNode(target);
        window.getSelection().removeAllRanges();
        window.getSelection().addRange(range);
        document.execCommand('copy');
        window.getSelection().removeAllRanges();
        console.log('复制成功')
    } catch (e) {
        console.log('复制失败')
    }

    if (attr) {
        // remove temp target
        target.parentElement.removeChild(target);
    }
}
function html2Escape(sHtml) {
 return sHtml.replace(/[<>&"]/g,function(c){return {'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c];});
}

/**
 * post 新窗口
 */
function postBlank(URL, PARAMS) {
    var temp_form = document.createElement("form");
    temp_form.action = URL;
    temp_form.target = "_blank";
    temp_form.method = "post";
    temp_form.style.display = "none";
    for (var x in PARAMS) {
        var opt = document.createElement("textarea");
        opt.name = x;
        opt.value = PARAMS[x];
        temp_form.appendChild(opt);
    }
    document.body.appendChild(temp_form);
    temp_form.submit();
    temp_form.remove();
}

/**
 * indexOf 工具, 返回非负较小值, 如果都为负, 则返回 -1
 **/
function minIdx(idx1, idx2) {
    if (idx1 < 0 && idx2 < 0) {
        return -1;
    } else if (idx1 < 0) {
        return idx2;
    } else if (idx2 < 0) {
        return idx1;
    } else {
        return Math.min(idx1, idx2)
    }
}

/**
 *  html 反转义 比如 &lt; 替换为 <, 并将 <br> 替换为换行符
 **/
function unescapeHtmlAndReplaceBr(encodedStr) {
    // 创建一个临时的div元素
    const tempDiv = document.createElement('div');

    // 设置div的innerHTML为传入的编码字符串
    tempDiv.innerHTML = encodedStr.replace(/<br\s*\/?>/gi, "###!!!###换行符###!!!###");

    // 获取div的textContent，这将反转义HTML实体
    let text = tempDiv.textContent || tempDiv.innerText;

    // 使用正则表达式替换所有的<br>标签为换行符
    text = text.replace(/###!!!###换行符###!!!###/gi, '\n');

    // 清理临时div
    tempDiv.remove();

    return text;
}

/*
 * 判断是否在SQL的引号 附近缺了空格 如果是 where a = '1'and 报错 where a = '1'<此处缺空格>and
 */
function notSpaceAfterEvenQuotes(str) {
    var quote = null;
    for (let i = 0; i < str.length; i++) {
        var s0 = i == 0 ? '' : str[i-1];
        var s1 = str[i];
        var s2 = i == str.length -1 ? '' : str[i+1];
        if (s0 === '/' && s1 === '*') {
            // 任何位置的 /* */
            quote = null;
            i++;
            for (;i < str.length; i++) {
                if (str[i-1] === '*' || str[i] === '/') {
                    break;
                }
            }
            continue;
        } else if (quote == null && ((s0 === '#' && /\s/.test(s1)) || (s0 === '-' && s1 === '-' && /\s/.test(s2)))) {
            // 不在引号内的 '# xxx' 注释 或者 '-- xxx' 注释
            quote = null;
            i++;
            for (;i < str.length; i++) {
                if (str[i] === '\r' || str[i] === '\n') {
                    break;
                }
            }
            continue;
        } else if (s1 === '\r' || s1 === '\n') {
            // 任何位置发现换行则清空 quote
            quote = null;
            continue;
        } else if (s0 === '\\') {
            // 前一个字符是\则不做任何处理
            continue;
        } else if (s1 === quote) {
            // 引号配对和验证引号后一个字符
            quote = null;
            if (/[a-zA-Z0-9]/.test(s2) && i != str.length - 1) {
                // 如果是 where a = '1'and 报错 where a = '1'<此处缺空格>and
                let pre = str.substring(Math.max(i - 48, 0), i + 1);
                let post = str.substring(i + 1, Math.min(i + 32, str.length - 1));
                return pre + "<<此处缺空格>>" + post;
            }
            continue;
        } else if (s1 === '"' || s1 === '\'' || s1 === '`') {
            // 发现引号则记录, 后续代码寻找后面的下一个引号
            if (quote == null) {
                // 如果不同类型的引号嵌套忽略内层引号 比如 "it's" 忽略单引号
                quote = s1;
            }
        }
    }
    return '';
}

/**
 * 修改 textarea 的 tab 宽度
 */
function adjustTabSize(value, textareaSelector) {
     const textareas = document.querySelectorAll(textareaSelector);
     textareas.forEach(textarea => {
         if (value === 'auto') {
             textarea.style.removeProperty('tab-size');
         } else {
             textarea.style.tabSize = value;
         }
     });
}
